package eiisan.forkjointest.countedcompleter;

import java.util.concurrent.CountedCompleter;

class MyOperation<E> {
    void apply(E e) {
        System.out.println(Thread.currentThread().getName() + " apply ===> " + e);
    }
}

public class ForEach<E> extends CountedCompleter<Void> {

    public static <E> void forEach(E[] array) {
        forEach(array, new MyOperation<E>());
    }

    public static <E> void forEach(E[] array, MyOperation<E> op) {
        new ForEach<E>(null, array, op, 0, array.length).invoke();
    }

    final E[] array;
    final MyOperation<E> op;
    final int lo, hi;

    ForEach(CountedCompleter<?> p, E[] array, MyOperation<E> op, int lo, int hi) {
        super(p);
        this.array = array;
        this.op = op;
        this.lo = lo;
        this.hi = hi;
    }

    @Override
    public void onCompletion(CountedCompleter<?> caller) {
        //忽略自己调自己.
        if (caller != this) {
            System.out.println("finish =====>>> ");
        }
    }

    public void compute1() { // version 1
        if (hi - lo >= 2) {
            int mid = (lo + hi) >>> 1;
            setPendingCount(2); // must set pending count before fork
            new ForEach(this, array, op, mid, hi).fork(); // right child
            new ForEach(this, array, op, lo, mid).fork(); // left child
        } else if (hi > lo)
            op.apply(array[lo]);
        tryComplete();
    }

    /**
     * 改进
     * 可以注意到，在递归情况下，任务在for它的右任务之后什么也不做，因此可以在返回之前直接调用它的左
     * 任务，从而改进这种设计。(这类似于尾部递归移除。)此外，由于任务在执行其左任务时返回(而不是通
     * 过调用{@code tryComplete})，挂起计数被设置为1:
     */
    public void compute2() { // version 2
        if (hi - lo >= 2) {
            int mid = (lo + hi) >>> 1;
            setPendingCount(1); // only one pending
            new ForEach(this, array, op, mid, hi).fork(); // right child
            new ForEach(this, array, op, lo, mid).compute(); // direct invoke
        } else {
            if (hi > lo)
                op.apply(array[lo]);
            tryComplete();
        }
    }

    /**
     * 再改进
     * 作为进一步的改进，请注意左边的任务甚至不需要存在。
     * 我们可以使用原始任务进行迭代，并为每个fork添加一个挂起计数，而不是创建一个新任务。
     * 此外，由于此树中没有任何任务实现{@link #onCompletion(CountedCompleter)}方法，
     * 所以{@code tryComplete()}可以替换为{@link #propagateCompletion}。
     * */
     //PS： 这里通过循环实质上分解了 原来‘左’边的任务，而‘右’边的任务，则通过new ForEach自身
     //的compute自行处理（即当成全部任务递归分解）。
    public void compute() { // version 3
        int l = lo, h = hi;
        while (h - l >= 2) {
            int mid = (l + h) >>> 1;
            addToPendingCount(1);
            new ForEach(this, array, op, mid, h).fork(); // right child
            h = mid;
        }
        if (h > l)
            op.apply(array[l]);
        // tryComplete会在判断链上某个completer的挂起任务数是0立即执行onCompletion.
        tryComplete();
    }

    public void compute4() { // version 4
        int l = lo, h = hi;
        while (h - l >= 2) {
            int mid = (l + h) >>> 1;
            addToPendingCount(1);
            new ForEach(this, array, op, mid, h).fork(); // right child
            h = mid;
        }
        if (h > l)
            op.apply(array[l]);
        //基本等效于tryComplete,只是不执行onCompletion,
        // tryComplete会在判断链上某个completer的挂起任务数是0立即执行onCompletion.
        propagateCompletion();
    }
}
